package com.lagou.factory;

import com.alibaba.druid.util.StringUtils;
import com.lagou.annotation.*;
import com.lagou.utils.SearchClassUtils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 采用注解方式生成bean工厂
 */
public class AnnotationBeanFactory {

    // bean池
    private static Map<String, Object> beanPool = new HashMap<>();

    // 需要进行事务代理处理的bean
    private static Map<String, Object> needTran = new HashMap<>();

    public static Map<String, Object> getBeanPool() {
        return beanPool;
    }

    public static void setBeanPool(Map<String, Object> beanPool) {
        AnnotationBeanFactory.beanPool = beanPool;
    }

    //扫描指定包下类是否有注解，通过反射技术将注解类注册到map中  默认扫描com.lagou.edu.service.impl包 和 com.lagou.edu.dao.impl包
    static {
        // 根据包先获取包下的类
        List<String> classes = getClasses("com.lagou");
        // 扫描每个类，进行bean初始化
        factoryClasses(classes);
        // 针对每个类中的方法进行判断，是否存在需要自动注入的bean
        injectFields();
        // 针对需要做事务处理的类进行事务代理处理
        transactionalProxyBeans();
    }

    /**
     * 根据包集合获取全限定类名
     *
     * @param packagesName 报名集合
     * @return 类集合
     */
    private static List<String> getClasses(String... packagesName) {
        return SearchClassUtils.getInstance().getClasses(packagesName);
    }

    /**
     * 生产bean
     *
     * @param classes bean集合
     */
    private static void factoryClasses(List<String> classes) {
        for (String aClass : classes) {
            if (aClass.contains("$")) {
                continue;
            }

            Class<?> clazz;
            try {
                clazz = Class.forName(aClass);
                factoryClass(clazz);

            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 生产单个bean
     *
     * @param clazz 需要处理的bean
     */
    private static void factoryClass(Class<?> clazz) {
        try {
            // 如果是注解类，直接返回即可
            if (clazz.isAnnotation()) {
                return;
            }
            //将 标有 Service、Repository、Component注解 的类注册到容器中
            String key = "";
            if (clazz.isAnnotationPresent(Service.class)) {
                Service service = clazz.getAnnotation(Service.class);
                key = generateKey(service.value(), clazz);
            } else if (clazz.isAnnotationPresent(Repository.class)) {
                Repository repository = clazz.getAnnotation(Repository.class);
                key = generateKey(repository.value(), clazz);
            } else if (clazz.isAnnotationPresent(Component.class)) {
                Component component = clazz.getAnnotation(Component.class);
                key = generateKey(component.value(), clazz);
            }

            if (StringUtils.isEmpty(key)) {
                return;
            }

            beanPool.put(key, clazz.newInstance());

            for (Method method : clazz.getDeclaredMethods()) {
                //如果包含Transactional 注解 对其生成代理对象 ，用代理对象将原来的对象替换
                if (method.isAnnotationPresent(Transactional.class)) {
                    //获取代理工厂
                    needTran.put(key, beanPool.get(key));
                }
            }

        } catch (IllegalAccessException | InstantiationException e) {
            e.printStackTrace();
        }
    }

    private static String generateKey(String name, Class<?> clazz) {
        return StringUtils.isEmpty(name) ?
                clazz.getSimpleName().substring(0, 1).toLowerCase() + clazz.getSimpleName().substring(1) :
                name;
    }

    /**
     * 检查每个bean中是否存在需要注入的字段，如果存在，则进行注入
     */
    private static void injectFields() {
        for (Map.Entry<String, Object> entry : beanPool.entrySet()) {
            Object value = entry.getValue();
            try {
                Class<?> clazz = value.getClass();
                Field[] declaredFields;
                declaredFields = clazz.getDeclaredFields();
                for (Field declaredField : declaredFields) {
                    if (!declaredField.isAnnotationPresent(Autowired.class)) {
                        continue;
                    }

                    String sb = "set" +
                            declaredField.getType().getSimpleName().substring(0, 1).toUpperCase() +
                            declaredField.getType().getSimpleName().substring(1);
                    Method method = clazz.getMethod(sb, declaredField.getType());

                    //如果是接口 实例化 接口的实现类
                    Class<?> targetClazz;
                    if (declaredField.getType().isInterface()) {
                        //这里默认为一个实现类。 多个实现类要是使用@Qulifier 注解去确定注入哪个实现类
                        List<Class<?>> imp = SearchClassUtils.getAllClassByInterface(declaredField.getType());
                        targetClazz = imp.get(0);
                    } else {
                        targetClazz = declaredField.getType();
                    }

                    String methodBeanKey = targetClazz.getSimpleName().substring(0, 1).toLowerCase()
                            + targetClazz.getSimpleName().substring(1);
                    Object target = beanPool.get(methodBeanKey);
                    method.invoke(value, target);
                }

                //反射注入依赖（通过setter 方法）
            } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 通过事务代理
     */
    private static void transactionalProxyBeans() {
        // 获取代理工厂
        TransactionalProxyFactory transactionalProxyFactory = (TransactionalProxyFactory) beanPool.get("transactionalProxyFactory");
        for (Map.Entry<String, Object> entry : needTran.entrySet()) {
            Object sourceValue = entry.getValue();
            sourceValue = transactionalProxyFactory.getJdkProxy(sourceValue);
            beanPool.put(entry.getKey(), sourceValue);
            needTran.remove(entry.getKey());
        }
    }

    public static Object getBean(String id) {
        return beanPool.get(id);
    }
}
